8.3 同步
通道并非用来取代锁的,它们有各自不同的使用场景。通道倾向于解决逻辑层次的并发处理架构,而锁则用来保护局部范围内的数据安全。
标准库sync提供了互斥和读写锁,另有原子操作等,可基本满足日常开发需要。Mutex、RWMutex的使用并不复杂,只有几个地方需要注意。
将Mutex作为匿名字段时,相关方法必须实现为pointer-receiver,否则会因复制导致锁机制失效。
type data struct{ sync.Mutex }
func(d data)test(s string) { d.Lock() defer d.Unlock()
for i:=0;i<5;i++ { println(s,i) time.Sleep(time.Second) } }
func main() { var wg sync.WaitGroup wg.Add(2)
var d data
go func() { defer wg.Done() d.test(“read”) }()
go func() { defer wg.Done() d.test(“write”) }()
wg.Wait() }
输出:
write 0 read 0 read 1 write 1 write 2 read 2 read 3 write 3 write 4 read 4
锁失效,将receiver类型改为*data后正常。
也可用嵌入*Mutex来避免复制问题,但那需要专门初始化。
应将Mutex锁粒度控制在最小范围内,及早释放。
// 错误用法
func doSomething() {
m.Lock()
url:=cache[“key”]
http.Get(url) // 该操作并不需要锁保护
m.Unlock()
}
// 正确用法 func doSomething() { m.Lock() url:=cache[“key”] m.Unlock() // 如使用defer,则依旧将Get保护在内 http.Get(url) }
Mutex不支持递归锁,即便在同一goroutine下也会导致死锁。
func main() { var m sync.Mutex
m.Lock() { m.Lock() m.Unlock() } m.Unlock() }
输出:
fatal error:all goroutines are asleep-deadlock!
在设计并发安全类型时,千万注意此类问题。
type cache struct{ sync.Mutex data[]int }
func(c*cache)count()int{ c.Lock() n:=len(c.data) c.Unlock()
return n }
func(c*cache)get()int{ c.Lock() defer c.Unlock()
var d int if n:=c.count();n>0{ //count重复锁定,导致死锁 d=c.data[0] c.data=c.data[1:] }
return d }
func main() { c:=cache{ data: []int{1,2,3,4}, }
println(c.get()) }
输出:
fatal error:all goroutines are asleep-deadlock!
相关建议:
- 对性能要求较高时,应避免使用defer Unlock。
- 读写并发时,用RWMutex性能会更好一些。
- 对单个数据读写保护,可尝试用原子操作。
- 执行严格测试,尽可能打开数据竞争检查。